/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "core/render/node/render_object.h"
#include <math.h>

#include "core/common/view_utils.h"
#include "core/css/constants_name.h"
#include "core/css/constants_value.h"
#include "core/css/css_value_getter.h"
#include "core/layout/layout.h"
#include "core/manager/weex_core_manager.h"
#include "core/render/manager/render_manager.h"
#include "core/render/page/render_page.h"

namespace WeexCore {

    RenderObject::RenderObject() : parent_render_(nullptr) {
        this->styles_ = new std::map<std::string, std::string>();
        this->attributes_ = new std::map<std::string, std::string>();
        this->events_ = new std::set<std::string>();
        this->is_root_render_ = false;
    }

    RenderObject::~RenderObject() {
        this->parent_render_ = nullptr;

        if (this->styles_ != nullptr) {
            delete this->styles_;
            this->styles_ = nullptr;
        }

        if (this->attributes_ != nullptr) {
            delete this->attributes_;
            this->attributes_ = nullptr;
        }

        if (this->events_ != nullptr) {
            delete this->events_;
            this->events_ = nullptr;
        }

        for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
            RenderObject *child = static_cast<RenderObject *>(*it);
            if (child != nullptr) {
                delete child;
            }
        }

        for (auto it: shadow_objects_) {
            delete it;
        }
    }

    void RenderObject::ApplyDefaultStyle(bool reserve) {
        std::map<std::string, std::string> *style = GetDefaultStyle();

        if (style == nullptr) return;

        for (auto iter = style->cbegin(); iter != style->cend(); iter++)
            AddStyle(iter->first, iter->second, reserve);

        if (style != nullptr) {
            delete style;
        }
    }

    RenderObject *RenderObject::RichtextParent() {
        if (parent_render_ && parent_render_->type() == "richtext") {
            return parent_render_;
        } else if (parent_render_) {
            return parent_render_->RichtextParent();
        }
        return nullptr;
    }

    void RenderObject::ApplyDefaultAttr() {
        std::map<std::string, std::string> *attrs = GetDefaultAttr();

        if (attrs == nullptr) return;

        for (auto iter = attrs->cbegin(); iter != attrs->cend(); iter++) {
            UpdateAttr(iter->first, iter->second);
        }

        if (attrs != nullptr) {
            delete attrs;
        }
    }

    static WXCoreSize measureFunc_Impl(WXCoreLayoutNode *node, float width,
                                       MeasureMode widthMeasureMode, float height,
                                       MeasureMode heightMeasureMode) {
        WXCoreSize size;
        size.height = 0;
        size.width = 0;

        if (!node->haveMeasureFunc()) return size;
        return WeexCoreManager::Instance()
                ->getPlatformBridge()
                ->platform_side()
                ->InvokeMeasureFunction(
                        static_cast<RenderObject *>(node)->page_id().c_str(),
                        reinterpret_cast<intptr_t>(node), width, widthMeasureMode, height,
                        heightMeasureMode);
    }

    void RenderObject::BindMeasureFunc() { setMeasureFunc(measureFunc_Impl); }

    void RenderObject::OnLayoutBefore() {
        if (!haveMeasureFunc()) return;
        WeexCoreManager::Instance()
                ->getPlatformBridge()
                ->platform_side()
                ->InvokeLayoutBefore(page_id().c_str(), reinterpret_cast<intptr_t>(this));
    }

    void RenderObject::OnLayoutPlatform() {
        if (!getNeedsPlatformDependentLayout()) return;
        WeexCoreManager::Instance()
                ->getPlatformBridge()
                ->platform_side()
                ->InvokeLayoutPlatform(page_id().c_str(), reinterpret_cast<intptr_t>(this));
    }

    void RenderObject::OnLayoutAfter(float width, float height) {
        if (!haveMeasureFunc()) return;
        WeexCoreManager::Instance()
                ->getPlatformBridge()
                ->platform_side()
                ->InvokeLayoutAfter(page_id().c_str(), reinterpret_cast<intptr_t>(this),
                                    width, height);
    }

    StyleType RenderObject::ApplyStyle(const std::string &key,
                                       const std::string &value,
                                       const bool updating) {
        bool insert = false;
        if (value.length() > 0 && (value.at(0) == JSON_OBJECT_MARK_CHAR ||
                                   value.at(0) == JSON_ARRAY_MARK_CHAR)) {
            MapInsertOrAssign(this->styles_, key, value);
            insert = true;
        }

        if (key == ALIGN_ITEMS) {
            setAlignItems(GetWXCoreAlignItem(value));
            return kTypeLayout;
        } else if (key == ALIGN_SELF) {
            setAlignSelf(GetWXCoreAlignSelf(value));
            return kTypeLayout;
        } else if (key == FLEX) {
            if (value.empty()) {
                set_flex(0);
            } else {
                float ret = getFloat(value.c_str());
                if (!isnan(ret)) {
                    set_flex(ret);
                }
            }
            return kTypeLayout;
        } else if (key == DIRECTION) {
            WeexCore::WXCoreDirection direction = GetWXCoreDirection(value);
            if (direction == WeexCore::kDirectionInherit && this->is_root_render_) {
                direction = WeexCore::kDirectionLTR;
            }
            setDirection(direction, updating);
            return kTypeInheritableLayout;
        } else if (key == FLEX_DIRECTION) {
            setFlexDirection(GetWXCoreFlexDirection(value), updating);
            return kTypeLayout;
        } else if (key == JUSTIFY_CONTENT) {
            setJustifyContent(GetWXCoreJustifyContent(value));
            return kTypeLayout;
        } else if (key == FLEX_WRAP) {
            setFlexWrap(GetWXCoreFlexWrap(value));
            return kTypeLayout;
        } else if (key == MIN_WIDTH) {
            UpdateStyleInternal(key, value, NAN,
                                [=](float foo) { setMinWidth(foo, updating); });
            return kTypeLayout;
        } else if (key == MIN_HEIGHT) {
            UpdateStyleInternal(key, value, NAN, [=](float foo) { setMinHeight(foo); });
            return kTypeLayout;
        } else if (key == MAX_WIDTH) {
            UpdateStyleInternal(key, value, NAN,
                                [=](float foo) { setMaxWidth(foo, updating); });
            return kTypeLayout;
        } else if (key == MAX_HEIGHT) {
            UpdateStyleInternal(key, value, NAN, [=](float foo) { setMaxHeight(foo); });
            return kTypeLayout;
        } else if (key == HEIGHT) {
            if (UpdateStyleInternal(key, value, NAN,
                                    [=](float foo) { setStyleHeight(foo); })) {
                setStyleHeightLevel(CSS_STYLE);
            }
            return kTypeLayout;
        } else if (key == WIDTH) {
            if (UpdateStyleInternal(key, value, NAN,
                                    [=](float foo) { setStyleWidth(foo, updating); })) {
                setStyleWidthLevel(CSS_STYLE);
            }
            return kTypeLayout;
        } else if (key == POSITION) {
            setStylePositionType(GetWXCorePositionType(value));
            if (value == STICKY) {
                this->is_sticky_ = true;
            }
            MapInsertOrAssign(this->styles_, key, value);
            return kTypeStyle;
        } else if (key == LEFT) {
            UpdateStyleInternal(key, value, NAN, [=](float foo) {
                setStylePosition(kPositionEdgeLeft, foo);
            });
            return kTypeLayout;
        } else if (key == TOP) {
            UpdateStyleInternal(key, value, NAN, [=](float foo) {
                setStylePosition(kPositionEdgeTop, foo);
            });
            return kTypeLayout;
        } else if (key == RIGHT) {
            UpdateStyleInternal(key, value, NAN, [=](float foo) {
                setStylePosition(kPositionEdgeRight, foo);
            });
            return kTypeLayout;
        } else if (key == BOTTOM) {
            UpdateStyleInternal(key, value, NAN, [=](float foo) {
                setStylePosition(kPositionEdgeBottom, foo);
            });
            return kTypeLayout;
        } else if (key == MARGIN) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setMargin(kMarginALL, foo); });
            return kTypeMargin;
        } else if (key == MARGIN_LEFT) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setMargin(kMarginLeft, foo); });
            return kTypeMargin;
        } else if (key == MARGIN_TOP) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setMargin(kMarginTop, foo); });
            return kTypeMargin;
        } else if (key == MARGIN_RIGHT) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setMargin(kMarginRight, foo); });
            return kTypeMargin;
        } else if (key == MARGIN_BOTTOM) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setMargin(kMarginBottom, foo); });
            return kTypeMargin;
        } else if (key == BORDER_WIDTH) {
            UpdateStyleInternal(key, value, 0, [=](float foo) {
                setBorderWidth(kBorderWidthALL, foo);
            });
            return kTypeBorder;
        } else if (key == BORDER_TOP_WIDTH) {
            UpdateStyleInternal(key, value, 0, [=](float foo) {
                setBorderWidth(kBorderWidthTop, foo);
            });
            return kTypeBorder;
        } else if (key == BORDER_RIGHT_WIDTH) {
            UpdateStyleInternal(key, value, 0, [=](float foo) {
                setBorderWidth(kBorderWidthRight, foo);
            });
            return kTypeBorder;
        } else if (key == BORDER_BOTTOM_WIDTH) {
            UpdateStyleInternal(key, value, 0, [=](float foo) {
                setBorderWidth(kBorderWidthBottom, foo);
            });
            return kTypeBorder;
        } else if (key == BORDER_LEFT_WIDTH) {
            UpdateStyleInternal(key, value, 0, [=](float foo) {
                setBorderWidth(kBorderWidthLeft, foo);
            });
            return kTypeBorder;
        } else if (key == PADDING) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setPadding(kPaddingALL, foo); });
            return kTypePadding;
        } else if (key == PADDING_LEFT) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setPadding(kPaddingLeft, foo); });
            return kTypePadding;
        } else if (key == PADDING_TOP) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setPadding(kPaddingTop, foo); });
            return kTypePadding;
        } else if (key == PADDING_RIGHT) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setPadding(kPaddingRight, foo); });
            return kTypePadding;
        } else if (key == PADDING_BOTTOM) {
            UpdateStyleInternal(key, value, 0,
                                [=](float foo) { setPadding(kPaddingBottom, foo); });
            return kTypePadding;
        } else {
            if (!insert) {
                MapInsertOrAssign(this->styles_, key, value);
            }
            return kTypeStyle;
        }
    }

    const std::string RenderObject::GetStyle(const std::string &key) {
        if (this->styles_ == nullptr) return "";

        std::map<std::string, std::string>::iterator iter = this->styles_->find(key);
        if (iter != this->styles_->end()) {
            return iter->second;
        } else {
            return "";
        }
    }

    const std::string RenderObject::GetAttr(const std::string &key) {
        if (this->attributes_ == nullptr) return "";

        std::map<std::string, std::string>::iterator iter =
                this->attributes_->find(key);
        if (iter != this->attributes_->end()) {
            return iter->second;
        } else {
            return "";
        }
    }

    bool RenderObject::hasShadow(const RenderObject *shadow) const {
        if (std::find(shadow_objects_.begin(), shadow_objects_.end(), shadow) != shadow_objects_.end()) {
            return true;
        } else {
            return false;
        }
    }

    int RenderObject::AddRenderObject(int index, RenderObject *child) {
        if (child == nullptr || index < -1) {
            return index;
        }

        if (type() == "richtext") {
            if (!hasShadow(child)) {
                shadow_objects_.push_back(child);
                child->set_parent_render(this);
            }
            return index;
        }

        Index count = getChildCount();
        index = index >= count ? -1 : index;
        if (index == -1) {
            addChildAt(child, getChildCount());
        } else {
            addChildAt(child, index);
        }

        child->set_parent_render(this);

        return index;
    }

    Index RenderObject::IndexOf(const RenderObject *render) {
        if (render == nullptr) {
            return -1;
        } else {
            int i = 0;
            for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
                RenderObject *child = static_cast<RenderObject *>(*it);
                if (child != nullptr) {
                    if (render->ref() == child->ref()) return i;
                }
                ++i;
            }
        }
        return -1;
    }

    bool RenderObject::UpdateStyleInternal(const std::string key,
                                           const std::string value, float fallback,
                                           std::function<void(float)> functor) {
        bool ret = false;
        if (value.empty()) {
            functor(fallback);
            ret = true;
        } else {
            float fvalue = getFloatByViewport(value,
                                              RenderManager::GetInstance()->viewport_width(page_id()),
                                              RenderManager::GetInstance()->DeviceWidth(page_id()),
#if OS_IOS
                    // reduce a map search on iOS
                    false
#else
                                              RenderManager::GetInstance()->round_off_deviation(page_id())
#endif
            );
            if (!isnan(fvalue)) {
                functor(fvalue);
                ret = true;
            }
        }
        return ret;
    }

    void RenderObject::LayoutBeforeImpl() {
        if (isDirty()) {
            OnLayoutBefore();
        }

        for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
            RenderObject *child = static_cast<RenderObject *>(*it);
            if (child != nullptr) {
                child->LayoutBeforeImpl();
            }
        }
    }

    void RenderObject::LayoutPlatformImpl() {
        if (hasNewLayout()) {
            OnLayoutPlatform();
        }

        for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
            RenderObject *child = static_cast<RenderObject *>(*it);
            if (child != nullptr) {
                child->LayoutPlatformImpl();
            }
        }
    }

    void RenderObject::LayoutAfterImpl() {
        if (hasNewLayout()) {
            OnLayoutAfter(getLayoutWidth(), getLayoutHeight());
        }

        for (auto it = ChildListIterBegin(); it != ChildListIterEnd(); it++) {
            RenderObject *child = static_cast<RenderObject *>(*it);
            if (child != nullptr) {
                child->LayoutAfterImpl();
            }
        }
    }

    void RenderObject::CopyFrom(RenderObject *src) {
        IRenderObject::CopyFrom(src);
        this->styles_->insert(src->styles_->begin(), src->styles_->end());
        this->attributes_->insert(src->attributes_->begin(), src->attributes_->end());
        this->events_->insert(src->events_->begin(), src->events_->end());
    }

    void RenderObject::MapInsertOrAssign(
            std::map<std::string, std::string> *targetMap, const std::string &key,
            const std::string &value) {
        std::map<std::string, std::string>::iterator it = targetMap->find(key);
        if (it != targetMap->end()) {
            it->second = value;
        } else {
            targetMap->insert({key, value});
        }
    }

    bool RenderObject::ViewInit() {
        return (!isnan(getStyleWidth()) && getStyleWidth() > 0) ||
               (is_root_render() && GetRenderPage() != nullptr &&
                GetRenderPage()->is_render_container_width_wrap_content());
    }

    RenderPage *RenderObject::GetRenderPage() {
        return static_cast<RenderPage *>(RenderManager::GetInstance()->GetPage(page_id()));
    }

    bool RenderObject::IsAppendTree() {
        std::string append = GetAttr(APPEND);
        if (append == "tree") {
            return true;
        }
        return false;
    }

    void RenderObject::UpdateAttr(std::string key, std::string value) {
        MapInsertOrAssign(this->attributes_, key, value);
    }

    StyleType RenderObject::UpdateStyle(std::string key, std::string value) {
        return ApplyStyle(key, value, true);
    }

    void RenderObject::MergeStyles(std::vector<std::pair<std::string, std::string>> *src) {
        if (src) {
            for (auto &p: *src) {
                MapInsertOrAssign(styles_, p.first, p.second);
            }
        }
    }

    RenderObject *RenderObject::GetChild(const Index &index) {
        return static_cast<RenderObject *>(getChildAt(index));
    }

    void RenderObject::RemoveRenderObject(RenderObject *child) {
        if (type() == "richtext") {
            int index = 0;
            for (auto it: shadow_objects_) {
                if (it == child) {
                    shadow_objects_.erase(shadow_objects_.begin() + index);
                    return;
                }
                index++;
            }
        } else {
            removeChild(child);
        }
    }

    void RenderObject::AddAttr(std::string key, std::string value) {
        MapInsertOrAssign(this->attributes_, key, value);
    }

    StyleType RenderObject::AddStyle(std::string key, std::string value, bool reserve) {
        if (reserve) {
            MapInsertOrAssign(styles_, key, value);
        }
        return ApplyStyle(key, value, false);
    }

    void RenderObject::AddEvent(std::string event) {
        if (this->events_ == nullptr) {
            this->events_ = new std::set<std::string>();
        }
        this->events_->insert(event);
    }

    void RenderObject::RemoveEvent(const std::string &event) {
        this->events_->erase(event);
    }
}  // namespace WeexCore
